package com.ly.mp.dal.comm.jdbc;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Role;
import org.springframework.core.NestedRuntimeException;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;

import com.ly.mp.dal.comm.annotation.MpDataSource;

@Role(value = BeanDefinition.ROLE_APPLICATION)
public class ReadWriteDataSourceProcessor implements BeanPostProcessor {
    private static final Logger log = LoggerFactory.getLogger(ReadWriteDataSourceProcessor.class);
    private boolean forceChoiceReadWhenWrite = false;
    private Map<String, Boolean> readMethodMap = new HashMap<>();
    private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>();
    private static Map<String, Boolean> methodIsReadCache = new ConcurrentHashMap<>();

    public static void clearProcessorDataSource() {
        dataSourceHolder.remove();
    }

    public static void setProcessorDataSource(String dsKey) {
        dataSourceHolder.set(dsKey);
    }

    public void setForceChoiceReadWhenWrite(boolean forceChoiceReadWhenWrite) {
        this.forceChoiceReadWhenWrite = forceChoiceReadWhenWrite;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!(bean instanceof NameMatchTransactionAttributeSource)) {
            return bean;
        } else {
            try {
                NameMatchTransactionAttributeSource transactionAttributeSource = (NameMatchTransactionAttributeSource) bean;
                Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");
                ReflectionUtils.makeAccessible(nameMapField);
                Map<String, TransactionAttribute> nameMap = (Map) nameMapField.get(transactionAttributeSource);
                Iterator var6 = nameMap.entrySet().iterator();

                while (var6.hasNext()) {
                    Map.Entry<String, TransactionAttribute> entry = (Map.Entry) var6.next();
                    RuleBasedTransactionAttribute attr = (RuleBasedTransactionAttribute) entry.getValue();
                    if (attr.isReadOnly()) {
                        String methodName = entry.getKey();
                        Boolean isForceChoiceRead = Boolean.FALSE;
                        if (this.forceChoiceReadWhenWrite) {
                            attr.setPropagationBehavior(Propagation.NOT_SUPPORTED.value());
                            isForceChoiceRead = Boolean.TRUE;
                        } else {
                            attr.setPropagationBehavior(Propagation.SUPPORTS.value());
                        }

                        log.info("read/write transaction process  method:{} force read:{}", methodName,
                                isForceChoiceRead);
                        this.readMethodMap.put(methodName, isForceChoiceRead);
                    }
                }

                return bean;
            } catch (Exception var11) {
                throw new ReadWriteDataSourceTransactionException("process read/write transaction error", var11);
            }
        }
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * readOnly=true的只能走读库, 通过 MpDataSource, 或者 holder , 只能写default, 或者不写
     * readOnly=false的只能走写库, 通过 MpDataSource, 或者 holder , 只能写default, 或者不写
     */
    public Object determineReadOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Object target = pjp.getTarget();
        String cacheKey = target.getClass().getName() + "." + method.getName();
        Boolean isReadCacheValue = methodIsReadCache.get(cacheKey);
        if (isReadCacheValue == null) {
            isReadCacheValue = this.isChoiceReadDB(method);
            methodIsReadCache.put(cacheKey, isReadCacheValue);
            log.info("缓存方法数为{},读写方式{}", methodIsReadCache.size(), isReadCacheValue);
        }

        String dsPrefixed = this.getDataSourceIdPrefixed(method);
        if (isReadCacheValue) {
            ReadWriteDataSourceDecision.markRead(dsPrefixed);
        } else {
            ReadWriteDataSourceDecision.markWrite(dsPrefixed);
        }

        Object var7;
        try {
            var7 = pjp.proceed();
        } finally {
            ReadWriteDataSourceDecision.reset();
        }

        return var7;
    }

    private String getDataSourceIdPrefixed(Method method) {
        MpDataSource dataSourceAnno = AnnotationUtils.findAnnotation(method, MpDataSource.class);
        if (dataSourceAnno != null && dataSourceAnno.cascading()) {
            dataSourceHolder.set(dataSourceAnno.value());
        } else if (dataSourceAnno != null && !dataSourceAnno.cascading()) {
            dataSourceHolder.remove();
        }

        if (dataSourceHolder.get() != null) {
            return dataSourceHolder.get();
        } else {
            return dataSourceAnno == null ? null : dataSourceAnno.value();
        }
    }

    /**
     * 判断读写库的切换逻辑
     */
    private boolean isChoiceReadDB(Method method) {
        Transactional transactionalAnno = AnnotationUtils.findAnnotation(method, Transactional.class);
        if (transactionalAnno == null) {
            return this.isChoiceReadDB(method.getName());
        } else if (ReadWriteDataSourceDecision.isChoiceWrite()) {
            return false;
        } else {
            return transactionalAnno.readOnly();
        }
    }

    private Boolean isChoiceReadDB(String methodName) {
        String bestNameMatch = null;
        Iterator var3 = this.readMethodMap.keySet().iterator();

        while (var3.hasNext()) {
            String mappedName = (String) var3.next();
            if (this.isMatch(methodName, mappedName)) {
                bestNameMatch = mappedName;
                break;
            }
        }

        Boolean isForceChoiceRead = this.readMethodMap.get(bestNameMatch);
        if (isForceChoiceRead == Boolean.TRUE) {
            return true;
        } else if (ReadWriteDataSourceDecision.isChoiceWrite()) {
            return false;
        } else {
            return isForceChoiceRead != null;
        }
    }

    protected boolean isMatch(String methodName, String mappedName) {
        return PatternMatchUtils.simpleMatch(mappedName, methodName);
    }

    private class ReadWriteDataSourceTransactionException extends NestedRuntimeException {
        private static final long serialVersionUID = -7831040288687947725L;

        public ReadWriteDataSourceTransactionException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}